package ru.passivemoney.interface_for_server;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import ru.passivemoney.common.UtilsOrders;
import ru.passivemoney.common.UtilsOrders.ActionUnderOrder;
import ru.passivemoney.manage.account.AccountHolder;
import ru.passivemoney.manage.account.InstrumentsHolder;
import ru.passivemoney.manage.job.Scheduller;
import ru.passivemoney.manage.messages.MessagesHolder;
import ru.passivemoney.root.ContextHolder;
import ru.passivemoney.root.EngineHolder;
import ru.passivemoney.subscribers.ASubscriber;

import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IOrder.State;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.Period;

public class Notifier implements IStrategy {

	private static final Logger logger = LoggerFactory.getLogger(Notifier.class);
	
	private static final boolean CLOSE_ORDERS_WHEN_STOP = true;
	
	private Map<Instrument, List<ASubscriber>> instrumentSubscribersMap = new ConcurrentHashMap<Instrument, List<ASubscriber>>();
	private Map<String, List<ASubscriber>> instrumentPeriodSubscribersMap = new ConcurrentHashMap<String, List<ASubscriber>>();
	private List<ASubscriber> subscribers = new ArrayList<ASubscriber>();
	
	public Notifier(List<ASubscriber> subscribers) {
		this.subscribers = subscribers;
		init();
	}

	@Override
	public void onStart(IContext context) throws JFException {
		ContextHolder.setContext(context);
		EngineHolder.setEngine(context.getEngine());
		AccountHolder.setCurrency(context.getAccount().getCurrency());
		
		for(ASubscriber subscriber : subscribers){
			subscriber.initStrategy();
		}
		
		Scheduller.start();
	}
	
	@Override
	public void onTick(final Instrument instrument, final ITick tick) throws JFException {
		
		if(!InstrumentsHolder.getInstruments().contains(instrument)) return;

		new Thread(new Runnable(){
			public void run() {
				try{
					MDC.put("ROUTINGKEY", instrument.getPrimaryCurrency().toString() + instrument.getSecondaryCurrency().toString());
					List<ASubscriber> subscribers = instrumentSubscribersMap.get(instrument);
					if(subscribers != null){
						for(ASubscriber s : subscribers){
							s.reactOnTick(tick);
						}
					}
				}finally{
					MDC.remove("ROUTINGKEY");
				}
			}
		}, "tick_" + instrument + "_a" + tick.getAsk() + "_b" + tick.getBid()).start();
	}

	@Override
	public void onBar(final Instrument instrument, final Period period, final IBar askBar, final IBar bidBar) throws JFException {
		
		if(!InstrumentsHolder.getInstruments().contains(instrument)) return;
		
		new Thread(new Runnable(){
			public void run() {
				try{
					MDC.put("ROUTINGKEY", instrument.getPrimaryCurrency().toString() + instrument.getSecondaryCurrency().toString());
					List<ASubscriber> subscribers = instrumentPeriodSubscribersMap.get(instrument.toString() + period);
					if(subscribers != null){
						for(ASubscriber s : subscribers){
							try {
								s.reactOnBar(period, askBar, bidBar);
							} catch (JFException e) {
								//TODO: what if exception when signalBar on subscriber ?
							}
						}
					}
				}finally{
					MDC.remove("ROUTINGKEY");
				}
			}
		}, "bar_" + instrument + "_p" + period).start();
	}

	//TODO: review onMessage method
	@Override
	public void onMessage(IMessage message) throws JFException {
		logger.info("MESSAGE: {}", message);
		switch(message.getType()){
			case ORDER_CHANGED_OK:
			case ORDER_CHANGED_REJECTED:
				MessagesHolder.put(UtilsOrders.keyForSharedMap(message.getOrder(), ActionUnderOrder.CHANGE), message);
				break;
			case ORDER_CLOSE_OK:
			case ORDER_CLOSE_REJECTED:
				MessagesHolder.put(UtilsOrders.keyForSharedMap(message.getOrder(), ActionUnderOrder.CLOSE), message);
				break;
			case ORDER_SUBMIT_OK:
			case ORDER_SUBMIT_REJECTED:
				MessagesHolder.put(UtilsOrders.keyForSharedMap(message.getOrder(), ActionUnderOrder.SUBMIT), message);
				break;
			default:
				break;
		}
	}

	//TODO: review onAccount method
	@Override
	public void onAccount(IAccount account) throws JFException {
		logger.info("ACCOUNT: {}", account);
	}

	@Override
	public void onStop() throws JFException {
		if(CLOSE_ORDERS_WHEN_STOP){
			boolean closed = false;
			while(!closed){
				try{
					for (IOrder order : EngineHolder.getEngine().getOrders()) {
						if(State.OPENED.equals(order.getState()) || State.FILLED.equals(order.getState()))
							order.close();
					}
					closed = true;
					logger.info("Notifier stopped");
				}catch(Exception e){
					logger.error("Error when closing order.", e);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e1) {
					}
				}
			}
	        
		}
	}

	private void init() {
		instrumentSubscribersMap = new HashMap<Instrument, List<ASubscriber>>();
		instrumentPeriodSubscribersMap = new HashMap<String, List<ASubscriber>>();
		
		for(ASubscriber s : subscribers){
			if(instrumentSubscribersMap.get(s.getInstrument()) == null){
				instrumentSubscribersMap.put(s.getInstrument(), new ArrayList<ASubscriber>());
			}
			instrumentSubscribersMap.get(s.getInstrument()).add(s);
			
			if(s.getPeriods() != null && s.getPeriods().size() > 0){
				for(Period p : s.getPeriods()){
					String key = s.getInstrument().toString() + p;
					if(instrumentPeriodSubscribersMap.get(key) == null){
						instrumentPeriodSubscribersMap.put(key, new ArrayList<ASubscriber>());
					}
					instrumentPeriodSubscribersMap.get(key).add(s);
				}
			}
		}
	}
	
}
